//C++ header - Open Scene Graph - Copyright (C) 1998-2002 Robert Osfield
//Distributed under the terms of the GNU Library General Public License (LGPL)
//as published by the Free Software Foundation.

#include <osg/Export>
#include <osg/StateSet>
#include <osg/Matrix>

#include <osg/FrameStamp>
#include <osg/DisplaySettings>
#include <osg/Polytope>

#include <osg/GLExtensions>

#include <vector>
#include <map>


/** State class for managing a state stack.
  * Lazy state updating is used to minimize state changes.
  */
class State : public Referenced
{
    public :
    
        State();
        
        virtual ~State();

        /** push stateset onto state stack.*/
        void pushStateSet(const StateSet* dstate);

        /** pop drawstate off state stack.*/
        void popStateSet();
       
        /** copy the modes and attributes which captures the current state.*/
        void captureCurrentState(StateSet& stateset) const;

        /** reset the state object to an empty stack.*/
        void reset();

        inline void applyProjectionMatrix(const osg::Matrix* matrix)
        {
            if (_projection!=matrix)
            {
                glMatrixMode( GL_PROJECTION );
                if (matrix) 
                {
                    _projection=matrix;
                    glLoadMatrixf(matrix->ptr());
                }
                else
                {
                    _projection=_identity;
                    glLoadIdentity();
                }
                glMatrixMode( GL_MODELVIEW );
            }
        }
        
        const osg::Matrix& getProjectionMatrix() const
        {
            return *_projection;
        }

        inline void applyModelViewMatrix(const osg::Matrix* matrix)
        {
            if (_modelView!=matrix)
            {
                if (matrix)
                {
                    _modelView=matrix;
                    glLoadMatrixf(matrix->ptr());
                }
                else 
                {
                    _modelView=_identity;
                    glLoadIdentity();
                }
            }
        }

        const osg::Matrix& getModelViewMatrix() const
        {
            return *_modelView;
        }


        Polytope getViewFrustum() const;


        /** Apply stateset.*/
        void apply(const StateSet* dstate);

        /** Apply the state.*/
        void apply();


        /** Apply an OpenGL mode if required. */
        inline const bool applyMode(const StateAttribute::GLMode mode,const bool enabled)
        {
            ModeStack& ms = _modeMap[mode];
            ms.changed = true;    
            return applyMode(mode,enabled,ms);
        }

        inline const bool applyTextureMode(unsigned int unit, const StateAttribute::GLMode mode,const bool enabled)
        {
            ModeMap& modeMap = getOrCreateTextureModeMap(unit);
            ModeStack& ms = modeMap[mode];
            ms.changed = true;
            return applyMode(mode,enabled,ms);
        }

        /** Apply an attribute if required. */
        inline const bool applyAttribute(const StateAttribute* attribute)
        {
            AttributeStack& as = _attributeMap[attribute->getType()];
            as.changed = true;
            return applyAttribute(attribute,as);
        }

        inline const bool applyTextureAttribute(unsigned int unit, const StateAttribute* attribute)
        {
            AttributeMap& attributeMap = getOrCreateTextureAttributeMap(unit);
            AttributeStack& as = attributeMap[attribute->getType()];
            as.changed = true;
            return applyAttribute(attribute,as);
        }
       
        /** Mode has been set externally, update state to reflect this setting.*/
        void haveAppliedMode(const StateAttribute::GLMode mode,const StateAttribute::GLModeValue value);
        
        /** Mode has been set externally, therefore dirty the associated mode in osg::State
          * so it is applied on next call to osg::State::apply(..)*/
        void haveAppliedMode(const StateAttribute::GLMode mode);

        /** Attribute has been applied externally, update state to reflect this setting.*/
        void haveAppliedAttribute(const StateAttribute* attribute);

        /** Attribute has been applied externally, 
          * and therefore this attribute type has been dirtied 
          * and will need to be re-appplied on next osg::State.apply(..).
          * note, if you have an osg::StateAttribute which you have applied externally
          * then use the have_applied(attribute) method as this will the osg::State to
          * track the current state more accuratly and enable lazy state updating such
          * that only changed state will be applied.*/
        void haveAppliedAttribute(const StateAttribute::Type type);

        /** Get whether the current specified mode is enabled (true) or disabled (false).*/ 
        const bool getLastAppliedMode(const StateAttribute::GLMode mode) const;
        
        /** Get the current specified attribute, return NULL is one has not yet been applied.*/ 
        const StateAttribute* getLastAppliedAttribute(const StateAttribute::Type type) const;
        


        /** texture Mode has been set externally, update state to reflect this setting.*/
        void haveAppliedTextureMode(unsigned int unit, const StateAttribute::GLMode mode,const StateAttribute::GLModeValue value);
        
        /** texture Mode has been set externally, therefore dirty the associated mode in osg::State
          * so it is applied on next call to osg::State::apply(..)*/
        void haveAppliedTextureMode(unsigned int unit, const StateAttribute::GLMode mode);

        /** texture Attribute has been applied externally, update state to reflect this setting.*/
        void haveAppliedTextureAttribute(unsigned int unit, const StateAttribute* attribute);

        /** texture Attribute has been applied externally, 
          * and therefore this attribute type has been dirtied 
          * and will need to be re-appplied on next osg::State.apply(..).
          * note, if you have an osg::StateAttribute which you have applied externally
          * then use the have_applied(attribute) method as this will the osg::State to
          * track the current state more accuratly and enable lazy state updating such
          * that only changed state will be applied.*/
        void haveAppliedTextureAttribute(unsigned int unit, const StateAttribute::Type type);

        /** Get whether the current specified texture mode is enabled (true) or disabled (false).*/ 
        const bool getLastAppliedTextureMode(unsigned int unit, const StateAttribute::GLMode mode) const;
        
        /** Get the current specified texture attribute, return NULL is one has not yet been applied.*/ 
        const StateAttribute* getLastAppliedTextureAttribute(unsigned int unit, const StateAttribute::Type type) const;



        /** wrapper around glEnableClientState(GL_VERTEX_ARRAY);glVertexPointer(..);
          * note, only updates values that change.*/
        inline void setVertexPointer( GLint size, GLenum type,
                                      GLsizei stride, const GLvoid *ptr )
        {
            if (!_vertexArray._enabled)
            {
                _vertexArray._enabled = true;
                glEnableClientState(GL_VERTEX_ARRAY);
            }
            if (_vertexArray._pointer!=ptr)
            {
                _vertexArray._pointer=ptr;
                glVertexPointer( size, type, stride, ptr );
            }
        }
        
        /** wrapper glDisableClientState(GL_VERTEX_ARRAY).
          * note, only updates values that change.*/
        inline void disableVertexPointer()
        {
            if (_vertexArray._enabled)
            {
                _vertexArray._enabled = false;
                glDisableClientState(GL_VERTEX_ARRAY);
            }
        }

        /** wrapper around glEnableClientState(GL_NORMAL_ARRAY);glNormalPointer(..);
          * note, only updates values that change.*/
        inline void setNormalPointer( GLenum type, GLsizei stride,
                                      const GLvoid *ptr )
        {
            if (!_normalArray._enabled)
            {
                _normalArray._enabled = true;
                glEnableClientState(GL_NORMAL_ARRAY);
            }
            if (_normalArray._pointer!=ptr)
            {
                _normalArray._pointer=ptr;
                glNormalPointer( type, stride, ptr );
            }
        }

        /** wrapper around glDisableClientState(GL_NORMAL_ARRAY);
          * note, only updates values that change.*/
        inline void disableNormalPointer()
        {
            if (_normalArray._enabled)
            {
                _normalArray._enabled = false;
                glDisableClientState(GL_NORMAL_ARRAY);
            }
        }

        /** wrapper around glEnableClientState(GL_COLOR_ARRAY);glColorPointer(..);
          * note, only updates values that change.*/
        inline void setColorPointer( GLint size, GLenum type,
                                     GLsizei stride, const GLvoid *ptr )
        {
            if (!_colorArray._enabled)
            {
                _colorArray._enabled = true;
                glEnableClientState(GL_COLOR_ARRAY);
            }
            if (_colorArray._pointer!=ptr)
            {
                _colorArray._pointer=ptr;
                glColorPointer( size, type, stride, ptr );
            }
        }

        /** wrapper around glDisableClientState(GL_COLOR_ARRAY);
          * note, only updates values that change.*/
        inline void disableColorPointer()
        {
            if (_colorArray._enabled)
            {
                _colorArray._enabled = false;
                glDisableClientState(GL_COLOR_ARRAY);
            }
        }

        /** wrapper around glEnableClientState(GL_INDEX_ARRAY);glIndexPointer(..);
          * note, only updates values that change.*/
        inline void setIndexPointer( GLenum type, GLsizei stride,
                              const GLvoid *ptr )
        {
            if (!_indexArray._enabled)
            {
                _indexArray._enabled = true;
                glEnableClientState(GL_INDEX_ARRAY);
            }
            if (_indexArray._pointer!=ptr)
            {
                _indexArray._pointer=ptr;
                glIndexPointer( type, stride, ptr );
            }
        }

        /** wrapper around glDisableClientState(GL_INDEX_ARRAY);
          * note, only updates values that change.*/
        inline void disableIndexPointer()
        {
            if (_indexArray._enabled)
            {
                _indexArray._enabled = false;
                glDisableClientState(GL_INDEX_ARRAY);
            }
        }

        /** wrapper around glEnableClientState(GL_TEXTURE_COORD_ARRAY);glTexCoordPointer(..);
          * note, only updates values that change.*/
        inline void setTexCoordPointer( unsigned int unit,
                                 GLint size, GLenum type,
                                 GLsizei stride, const GLvoid *ptr )
        {
            if (setClientActiveTextureUnit(unit))
            {
                if ( unit >= _texCoordArrayList.size()) _texCoordArrayList.resize(unit+1);
                EnabledArrayPair& eap = _texCoordArrayList[unit];

                if (!eap._enabled)
                {
                    eap._enabled = true;
                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                }
                if (eap._pointer!=ptr)
                {
                    glTexCoordPointer( size, type, stride, ptr );
                    eap._pointer = ptr;
                }
            }
        }
        
        /** wrapper around glDisableClientState(GL_TEXTURE_COORD_ARRAY);
          * note, only updates values that change.*/
        inline void disableTexCoordPointer( unsigned int unit )
        {
            if (setClientActiveTextureUnit(unit))
            {
                if ( unit >= _texCoordArrayList.size()) _texCoordArrayList.resize(unit+1);
                EnabledArrayPair& eap = _texCoordArrayList[unit];

                if (eap._enabled)
                {
                    eap._enabled = false;
                    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                }
            }
        }

        inline void disableTexCoordPointersAboveAndIncluding( unsigned int unit )
        {
            while (unit<_texCoordArrayList.size())
            {
                EnabledArrayPair& eap = _texCoordArrayList[unit];
                if (eap._enabled)
                {
                    if (setClientActiveTextureUnit(unit))
                    {
                        eap._enabled = false;
                        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
                    }
                }
                ++unit;
            }
        }

        /** set the current tex coord array texture unit, return true if selected, false if selection failed such as when multitexturing is not supported.
          * note, only updates values that change.*/
        bool setClientActiveTextureUnit( unsigned int unit );


        /** set the current texture unit, return true if selected, false if selection failed such as when multitexturing is not supported.
          * note, only updates values that change.*/
        bool setActiveTextureUnit( unsigned int unit );
        

        
        /** Set the current OpenGL context uniqueID.
            Note, it is the application developers responsibility to
            set up unique ID for each OpenGL context.  This value is
            then used by osg::StateAttribure's and osg::Drawable's to
            help manage OpenGL display list and texture binds appropriate
            for each context.*/ 
        inline void setContextID(unsigned int contextID) { _contextID=contextID; }

        /** Get the current OpenGL context unique ID.*/ 
        inline const unsigned int getContextID() const { return _contextID; }
        
        
        /** Set the frame stamp for the current frame.*/
        inline void setFrameStamp(FrameStamp* fs) { _frameStamp = fs; }

        /** Set the frame stamp for the current frame.*/
        inline const FrameStamp* getFrameStamp() const { return _frameStamp.get(); }
        
                
        /** Set the DisplaySettings. Note, nothing is applied, the visual settings are just used
          * used in the State object to pass the current visual settings to Drawables
          * during rendering. */
        inline void setDisplaySettings(DisplaySettings* vs) { _displaySettings = vs; }
        
        /** Get the DisplaySettings */
        inline const DisplaySettings* getDisplaySettings() const { return _displaySettings.get(); }

        typedef std::pair<const StateAttribute*,StateAttribute::OverrideValue> AttributePair;
        typedef std::vector<AttributePair>                                     AttributeVec;
        typedef std::vector<StateAttribute::GLModeValue>  ValueVec;

    private:

    
        unsigned int                _contextID;
        ref_ptr<FrameStamp>         _frameStamp;
        
        ref_ptr<const Matrix>             _identity;
        ref_ptr<const Matrix>             _projection;
        ref_ptr<const Matrix>             _modelView;
        
        ref_ptr<DisplaySettings>    _displaySettings;
        

        struct ModeStack
        {
            ModeStack()
            {
                changed = false;
                last_applied_value = false;
                global_default_value = false;
            }
        
            bool        changed;
            bool        last_applied_value;
            bool        global_default_value;
            ValueVec    valueVec;
        };



        struct AttributeStack
        {
            AttributeStack()
            {
                changed = false;
                last_applied_attribute = 0L;
                global_default_attribute = 0L;
            }

            /** apply an attribute if required, passing in attribute and appropriate attribute stack */
            bool                    changed;
            const StateAttribute*   last_applied_attribute;
            ref_ptr<StateAttribute> global_default_attribute;
            AttributeVec            attributeVec;
        };

        /** apply an OpenGL mode if required, passing in mode, enable flag and appropriate mode stack */
        inline const bool applyMode(const StateAttribute::GLMode mode,const bool enabled,ModeStack& ms)
        {
            if (ms.last_applied_value != enabled)
            {
                ms.last_applied_value = enabled;

                if (enabled) glEnable(mode);
                else glDisable(mode);

                return true;
            }
            else
                return false;
        }

        /** apply an attribute if required, passing in attribute and appropriate attribute stack */
        inline const bool applyAttribute(const StateAttribute* attribute,AttributeStack& as)
        {
            if (as.last_applied_attribute != attribute)
            {
                if (!as.global_default_attribute.valid()) as.global_default_attribute = dynamic_cast<StateAttribute*>(attribute->cloneType());

                as.last_applied_attribute = attribute;
                attribute->apply(*this);
                return true;
            }
            else
                return false;
        }

        inline const bool applyGlobalDefaultAttribute(AttributeStack& as)
        {
            if (as.last_applied_attribute != as.global_default_attribute.get())
            {
                as.last_applied_attribute = as.global_default_attribute.get();
                if (as.global_default_attribute.valid()) as.global_default_attribute->apply(*this);
                return true;
            }
            else
                return false;
        }
        

        typedef std::map<StateAttribute::GLMode,ModeStack>      ModeMap;
        typedef std::vector<ModeMap>                            TextureModeMapList;

        typedef std::map<StateAttribute::Type,AttributeStack>   AttributeMap;
        typedef std::vector<AttributeMap>                       TextureAttributeMapList;

        typedef std::vector<ref_ptr<const StateSet> >           StateSetStack;
        typedef std::vector<ref_ptr<const Matrix> >             MatrixStack;

        ModeMap                                                 _modeMap;
        AttributeMap                                            _attributeMap;

        TextureModeMapList                                      _textureModeMapList;
        TextureAttributeMapList                                 _textureAttributeMapList;
       
        StateSetStack                                           _drawStateStack;
        
        struct EnabledArrayPair
        {
            EnabledArrayPair():_enabled(false),_pointer(0) {}
            EnabledArrayPair(const EnabledArrayPair& eap):_enabled(eap._enabled),_pointer(eap._pointer) {}
            EnabledArrayPair& operator = (const EnabledArrayPair& eap) { _enabled=eap._enabled; _pointer=eap._pointer; return *this; }
            
            bool          _enabled;
            const GLvoid* _pointer;
        };
        
        typedef std::vector<EnabledArrayPair>                   EnabledTexCoordArrayList;

        EnabledArrayPair            _vertexArray;
        EnabledArrayPair            _colorArray;
        EnabledArrayPair            _indexArray;
        EnabledArrayPair            _normalArray;
        EnabledTexCoordArrayList    _texCoordArrayList;
        
        unsigned int                _currentActiveTextureUnit;
        unsigned int                _currentClientActiveTextureUnit;
        
        inline ModeMap& getOrCreateTextureModeMap(unsigned int unit)
        {        
            if (unit>=_textureModeMapList.size()) _textureModeMapList.resize(unit+1);
            return _textureModeMapList[unit];
        }


        inline AttributeMap& getOrCreateTextureAttributeMap(unsigned int unit)
        {        
            if (unit>=_textureAttributeMapList.size()) _textureAttributeMapList.resize(unit+1);
            return _textureAttributeMapList[unit];
        }
        
        inline void pushModeList(ModeMap& modeMap,const StateSet::ModeList& modeList);
        inline void pushAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList);
        
        inline void popModeList(ModeMap& modeMap,const StateSet::ModeList& modeList);
        inline void popAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList);

        inline void applyModeList(ModeMap& modeMap,const StateSet::ModeList& modeList);
        inline void applyAttributeList(AttributeMap& attributeMap,const StateSet::AttributeList& attributeList);
        
        inline void applyModeMap(ModeMap& modeMap);
        inline void applyAttributeMap(AttributeMap& attributeMap);

        void haveAppliedMode(ModeMap& modeMap,const StateAttribute::GLMode mode,const StateAttribute::GLModeValue value);
        void haveAppliedMode(ModeMap& modeMap,const StateAttribute::GLMode mode);
        void haveAppliedAttribute(AttributeMap& attributeMap,const StateAttribute* attribute);
        void haveAppliedAttribute(AttributeMap& attributeMap,const StateAttribute::Type type);
        const bool getLastAppliedMode(const ModeMap& modeMap,const StateAttribute::GLMode mode) const;
        const StateAttribute* getLastAppliedAttribute(const AttributeMap& attributeMap,const StateAttribute::Type type) const;

};
